using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.IO.Compression;
using FreeImageAPI;
using FreeImageAPI.IO;
using FreeImageAPI.Plugins;

namespace Sample08
{
    public sealed class SerializationPlugin : LocalPlugin
    {
        // Header for the file
        private byte[] header = new byte[] { 0xff, 0x12, 0x0f, 0xff, 0x01, 0x00 };

        // Structure that will store all bitmap data.
        [Serializable]
        private struct SerialDib
        {
            public uint width;
            public uint height;
            public int pitch;
            public uint bpp;
            public uint red_mask;
            public uint green_mask;
            public uint blue_mask;
            public byte[] data;
        }

        // Implementation of 'GetImplementedMethods()'
        // All implemented methods are listed.
        protected override LocalPlugin.MethodFlags GetImplementedMethods()
        {
            return
                MethodFlags.DescriptionProc |
                MethodFlags.SupportsExportBPPProc |
                MethodFlags.SupportsExportTypeProc |
                MethodFlags.SupportsICCProfilesProc |
                MethodFlags.LoadProc |
                MethodFlags.SaveProc |
                MethodFlags.ValidateProc |
                MethodFlags.ExtensionListProc;
        }

        // Returns a format string.
        protected override string FormatProc()
        {
            return "Serialization";
        }

        // Returns a more specific description
        protected override string DescriptionProc()
        {
            return "Serializes bitmaps for .NET";
        }

        // Returns whether a color depth is supported.
        protected override bool SupportsExportBPPProc(int bpp)
        {
            return ((bpp == 1) ||
                    (bpp == 4) ||
                    (bpp == 8) ||
                    (bpp == 16) ||
                    (bpp == 24) ||
                    (bpp == 32));
        }

        // This plugin can only export standard bitmaps
        protected override bool SupportsExportTypeProc(FREE_IMAGE_TYPE type)
        {
            return (type == FREE_IMAGE_TYPE.FIT_BITMAP);
        }

        // This plugin does not support icc profiles
        protected override bool SupportsICCProfilesProc()
        {
            return false;
        }

        // The function reads the first bytes of the file and compares it
        // with the predefined header.
        protected override bool ValidateProc(ref FreeImageIO io, fi_handle handle)
        {
            for (int i = 0; i < header.Length; i++)
                if (ReadByte(io, handle) != header[i])
                    return false;
            return true;
        }

        // Loading function
        protected override FIBITMAP LoadProc(ref FreeImageIO io, fi_handle handle, int page, int flags, IntPtr data)
        {
            // Check if the data has the correct format
            if (!ValidateProc(ref io, handle))
            {
                // Create a free-image message
                FreeImage.OutputMessageProc(format, "Invalid format.");
                // return 0 (operation failed)
                return FIBITMAP.Zero;
            }

            SerialDib sdib;
            int read = 0;
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            byte[] buffer = new byte[1024];

            do
            {
                // Use the helper function 'Read' to read from the source
                read = Read(io, handle, 1, 1024, ref buffer);

                // Store the data in a temporary buffer
                stream.Write(buffer, 0, read);
            }
            while (read != 0);

            // Set the memory stream back to the beginning.
            stream.Position = 0;

            // Unzip the stream
            GZipStream zipStream = new GZipStream(stream, CompressionMode.Decompress);

            // Create a serializer
            BinaryFormatter formatter = new BinaryFormatter();

            // Deserialize the stream
            sdib = (SerialDib)formatter.Deserialize(zipStream);

            // Unload the stream
            zipStream.Dispose();

            // Use 'ConvertFromRawBits and the deserialized struct to recreate the bitmap
            // In this case the marshaller is used to create the needed IntPtr to the data
            // array.
            FIBITMAP dib = FreeImage.ConvertFromRawBits(
                Marshal.UnsafeAddrOfPinnedArrayElement(sdib.data, 0),
                (int)sdib.width, (int)sdib.height, sdib.pitch, sdib.bpp,
                sdib.red_mask, sdib.green_mask, sdib.blue_mask,
                false);

            // Unload the temporary stream
            stream.Dispose();

            // Return the created bitmap
            return dib;
        }

        // Saving function
        protected override bool SaveProc(ref FreeImageIO io, FIBITMAP dib, fi_handle handle, int page, int flags, IntPtr data)
        {
            SerialDib sdib;
            uint size = FreeImage.GetDIBSize(dib);

            // Store all data needed to recreate the bitmap
            sdib.width = FreeImage.GetWidth(dib);
            sdib.height = FreeImage.GetHeight(dib);
            sdib.pitch = (int)FreeImage.GetPitch(dib);
            sdib.bpp = FreeImage.GetBPP(dib);
            sdib.red_mask = FreeImage.GetRedMask(dib);
            sdib.green_mask = FreeImage.GetGreenMask(dib);
            sdib.blue_mask = FreeImage.GetBlueMask(dib);
            sdib.data = new byte[size];

            // Copy the bitmaps data into the structures byte-array
            // The marshaller is used to create an IntPtr for using
            // 'ConvertToRawBits'.
            FreeImage.ConvertToRawBits(Marshal.UnsafeAddrOfPinnedArrayElement(sdib.data, 0),
                          dib, sdib.pitch, sdib.bpp,
                          sdib.red_mask, sdib.green_mask, sdib.blue_mask,
                          false);

            // Use the healper function to write the header to the destination
            if (Write(io, handle, (uint)header.Length, 1, ref header) != 1)
                return false;

            // Create a serializer
            BinaryFormatter formatter = new BinaryFormatter();

            // Create a temporary stream
            MemoryStream stream = new MemoryStream();

            // Create a compression stream
            GZipStream zipStream = new GZipStream(stream, CompressionMode.Compress);

            // Serialize the structure into the compression stream
            formatter.Serialize(zipStream, sdib);

            // Unload the compression stream
            zipStream.Dispose();

            // Get the result data
            byte[] buffer = stream.GetBuffer();

            // Use the healper function 'Write' to write the data to the destination
            if (Write(io, handle, 1, (uint)buffer.Length, ref buffer) != buffer.Length)
            {
                // Unload the temporary stream
                stream.Dispose();
                return false;
            }

            // Unload the temporary stream
            stream.Dispose();
            return true;
        }

        // Return a list of supported file extensions (comma seperated)
        protected override string ExtensionListProc()
        {
            return "ser";
        }

        // Implementation of 'ToString()'
        public override string ToString()
        {
            return DescriptionProc();
        }
    }
}
